﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Threading;
using System.Xml;
using System.Xml.Linq;
using System.Windows.Forms;

using Perseus;
using Perseus.Data;
using Perseus.Plugins;

using Hetymine.Executioner.Commands;
using Hetymine.Executioner.Data;

namespace Hetymine.Executioner.Plugins.General.Notes {
    public class Notes : Plugin, IExecutable, ILoader, IDisplay, IConfigurable<IniFile> {
        private Dictionary<string, Note> _Notes;
        private NotesConfigurator _Configurator;
        private MenuItem _ContextMenu;
        private EventHandler _LoadingEvent;

        public Notes() {
            this.PluginInfo = PluginInfo.FromAssembly(
                "Notes",
                this.GetType().Assembly,
                "Blake Hancock",
                "http://hetymine.com/executioner/plugins/general/notes/"
            );

            this._LoadingEvent = new EventHandler(Executioner_Loading);
        }

        #region Executioner Events
        private void Executioner_Loading(object sender, EventArgs e) {
            if (Executioner.IsPluginLoaded("Notification Icon")) {
                this._ContextMenu = new MenuItem();
                this._ContextMenu.Text = "Notes";
                this._ContextMenu.Enabled = false;
                this.UpdateContextMenu();

                var p = Executioner.PluginService["Notification Icon"];
                p.Invoke("AddMenuItem", "Notes", this._ContextMenu);
            }
        }
        #endregion

        #region IExecutable
        public List<string> CallStrings { get; set; }
        public ExecutionResult Execute(string parameters) {
            /**
             * note $name
             * note show $name
             * note -s $name
             * 
             * note hide $name
             * note -h $name
             * 
             * note remove $name
             * note -r $name
             * 
             * note add $name $message
             * note -a $name $message
             * 
             * note -as $name $message 
             * 
             */

            var prams = new ParameterInfo(parameters);

            if (prams.IsEmpty) {
                App.Log("Notes: Insufficient number of parameters '" + parameters + "'.");
                return ExecutionResult.Failure;
            }

            // convert string style flags to flag style flags :D
            if (prams.Flags.Count == 0) {
                // If only one parameter, assume its the name of a note to be shown.
                if (prams.Parameters.Count == 1) {
                    prams.Flags.Add("s", new FlagInfo("s"));
                }
                else {
                    string flag = prams.Parameters[0];
                    prams.Parameters.RemoveAt(0);

                    switch (flag) {
                        case "show":
                            prams.Flags.Add("s", new FlagInfo("s"));
                            break;
                        case "hide":
                            prams.Flags.Add("h", new FlagInfo("h"));
                            break;
                        case "remove":
                            prams.Flags.Add("r", new FlagInfo("r"));
                            break;
                        case "add":
                            prams.Flags.Add("a", new FlagInfo("a"));
                            break;
                    }
                }
            }

            bool actionTaken = false;
            bool? result = null;

            if (prams.Flags.ContainsKey("a")) {
                if (prams.Parameters.Count < 2) {
                    this.AddNote(prams.Parameters[0], string.Empty);
                }
                else {
                    this.AddNote(prams.Parameters[0], prams.Join(1));
                }

                actionTaken = true;
                result = true;
            }

            if (prams.Flags.ContainsKey("t")) {
                int time = 0;
                if (prams.Flags["t"].Parameters.Count != 0) {
                    int.TryParse(prams.Flags["t"].Parameters[0], out time);
                }
                actionTaken = true;

                result = this.SetNoteTime(prams.Parameters[0], time);
            }

            if (result == false) {
                return ExecutionResult.Failure;
            }

            foreach (FlagInfo f in prams.Flags.Values) {
                switch (f.Flag) {
                    case "s":
                        result = this.ShowNote(prams.Parameters[0]);
                        actionTaken = true;
                        break;
                    case "h":
                        result = this.HideNote(prams.Parameters[0]);
                        actionTaken = true;
                        break;
                    case "r":
                        result = this.RemoveNote(prams.Parameters[0]);
                        actionTaken = true;
                        break;
                }

                if (result == false) {
                    break;
                }
            }

            if (!actionTaken) {
                App.Log("Notes: Invalid parameters '" + parameters + "'.");
                return ExecutionResult.Failure;
            }            

            return result.Value.ToExecutionResult();
        }
        #endregion

        #region Manage Notes
        private bool ShowNote(string name) {
            if (this._Notes.ContainsKey(name)) {
                this._Notes[name].IsVisible = true;
                this.UpdateContextMenu();

                return true;
            }

            return false;
        }
        private bool HideNote(string name) {
            if (this._Notes.ContainsKey(name)) {
                this._Notes[name].IsVisible = false;
                this.UpdateContextMenu();

                return true;
            }

            return false;
        }
        private bool RemoveNote(string name) {
            if (this._Notes.ContainsKey(name)) {
                this._Notes[name].IsVisible = false;
                this._Notes.Remove(name);
                this.UpdateContextMenu();

                return true;
            }

            return false;
        }
        private void AddNote(string name, string data) {
            if (this._Notes.ContainsKey(name)) {
                this._Notes[name].Data = data;
            }
            else {
                this._Notes.Add(name, this.CreateNote(name, data));
                this.UpdateContextMenu();
            }
        }
        private bool SetNoteTime(string name, int time) {
            if (this._Notes.ContainsKey(name)) {
                this._Notes[name].Time = time;

                return true;
            }

            return false;
        }

        private Note CreateNote(string name, string data) {
            Note n = new Note(name, data);
            n.NoteClosed += (o, e) => {
                this.UpdateContextMenu();
            };
            return n;
        }
        #endregion

        #region Context Menu
        private void ContextMenu_Click(object sender, EventArgs e) {
            var m = sender as MenuItem;
            string name = m.Tag.ToString();

            if (this._Notes.ContainsKey(name)) {
                this._Notes[name].IsVisible = !this._Notes[name].IsVisible;
                m.Checked = this._Notes[name].IsVisible;
            }
        }
        private void UpdateContextMenu() {
            this._ContextMenu.MenuItems.Clear();

            if (this._Notes.Count == 0) {
                this._ContextMenu.Enabled = false;
                return;
            }

            this._ContextMenu.Enabled = true;

            var sorted = from note in this._Notes 
                         orderby note.Key ascending 
                         select note.Value;

            foreach (var n in sorted) {
                var m = new MenuItem(n.Name, this.ContextMenu_Click);
                m.Checked = n.IsVisible;
                m.Tag = n.Name;
                this._ContextMenu.MenuItems.Add(m);
            }
        }
        #endregion

        #region ILoader
        public void Load() {
            Executioner.Loading += this._LoadingEvent;

            this._Notes = new Dictionary<string, Note>();
            
            string fileName = Executioner.PluginDirectory +
                "Notes" + Path.DirectorySeparatorChar;

            // Ensure the notes directory exists
            if (!Directory.Exists(fileName)) {
                Directory.CreateDirectory(fileName);
            }

            fileName += "notes.xml";

            if (File.Exists(fileName)) {
                XDocument xmlDoc = XDocument.Load(fileName);
                if (xmlDoc.Element("Notes") != null) {
                    var notes = from n in xmlDoc.Element("Notes").Elements("Note")
                                select n;

                    foreach (var n in notes) {
                        Note note = this.CreateNote(string.Empty, string.Empty);
                        note.Load(n);                        
                        this._Notes.Add(note.Name, note);                        
                    }
                }
            }

            this.CallStrings = new List<string>();
            string callString = Executioner.Config.GetString("Notes", "Call String", "note");
            this.CallStrings.Add(callString);
        }
        public void Unload() {
            Executioner.Loading -= this._LoadingEvent;

            string fileName = Executioner.PluginDirectory +
                "Notes" + Path.DirectorySeparatorChar +
                "notes.xml";

            if (this._Notes.Count > 0) {
                XmlWriterSettings xws = new XmlWriterSettings();
                xws.Encoding = System.Text.Encoding.UTF8;
                xws.Indent = true;
                xws.IndentChars = "    ";
                xws.NewLineChars = Environment.NewLine;
                xws.NewLineHandling = NewLineHandling.Replace;
                xws.OmitXmlDeclaration = true;

                XmlWriter xw = XmlWriter.Create(fileName, xws);

                XElement xe = new XElement("Notes");
                xe.Add(new XAttribute("Version", "1.0"));

                // Add command xelements                
                foreach (Note n in this._Notes.Values) {
                    XElement nxe = n.Save();
                    if (nxe != null) {
                        xe.Add(nxe);
                    }

                    // Hide the note after save in case reloading
                    n.IsVisible = false;
                }

                xe.WriteTo(xw);

                xw.Close();
            }
            else if (File.Exists(fileName)) {
                File.Delete(fileName);
            }

            this._Notes = null;
        }
        #endregion

        #region IDisplay
        public void Display(string data, int time) {            
            Display d = new Display(data, time, true);
            d.Show();
        }
        #endregion

        #region IConfigurable<IniFile>
        public IConfigurator<IniFile> Configurator {
            get {
                if (this._Configurator == null) {
                    this._Configurator = new NotesConfigurator();
                }

                return this._Configurator;
            }
        }
        #endregion
    }
}